// SDL2_14 [OpenGL ES Gears].nova // Interactive OpenGL ES gears. /* * Copyright (C) 1999-2001 Brian Paul All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Ported to GLES2. * Kristian Høgsberg * May 3, 2010 * * Improve GLES2 port: * * Refactor gear drawing. * * Use correct normals for surfaces. * * Improve shader. * * Use perspective projection transformation. * * Add FPS count. * * Add comments. * Alexandros Frantzis * Jul 13, 2010 */ /* * Converted to C++ / Nova / SDL2 / Emscripten. * Robert Platt * 29th October 2023 */ // Using namespace declarations. using library.emscripten; using library.math; using library.opengl; using library.sdl2; // The application class. class SDL2_14_OpenGL_ES_Gears { // Static data members. private static SDL_Window w; private static SDL_Renderer r; private static SDL_GLContext c; private static int sizeX, sizeY; private static int prevSizeX, prevSizeY; private static bool done; private static bool emscriptenActive; private static bool fullWindowMode; private static uint vertShader; private static uint fragShader; private static uint shaderProgram; private static int modelViewProjectionMatrix_location; private static int normalMatrix_location; private static int lightSourcePosition_location; private static int materialColor_location; private static float[] projectionMatrix; // The view rotation [ x, y, z ]. private static float[] view_rot; // The current gear rotation angle. private static float angle; private static int frames; private static double tRot0, tRate0; private static byte[] keyboardState; // The gears. private static Gear gear1, gear2, gear3; // Application class's "main" function. public static void main( String[] args ) { Stream.writeLine( "SDL2_14 [OpenGL ES Gears].nova" ); // Initialise the class's data members. sizeX = 800; sizeY = 600; prevSizeX = 0; prevSizeY = 0; done = false; emscriptenActive = Emscripten.isActive( ); fullWindowMode = false; frames = 0; tRot0 = -1.0; tRate0 = -1.0; // Output the keyboard contols. Stream.write( "\nKeyboard controls:\n" + " left = rotate y-axis +ve\n" + " right = rotate y-axis -ve\n" + " up = rotate x-axis +ve\n" + " down = rotate x-axis -ve\n" + " z = rotate z-axis +ve\n" + " shift + z = rotate z-axis -ve\n" ); if ( !emscriptenActive ) Stream.write( " esc = exit\n\n" ); else Stream.write( " w = toggle full window mode\n\n" ); // Disable the emscripten full-screen controls. Emscripten.disableControls( ); // Set the resize event callback. Emscripten.setResizeEventCallback( "SDL2_14_OpenGL_ES_Gears", resizeEventCallback ); // Setup SDL. SDL2.SDL_Init( SDL2.SDL_INIT_VIDEO ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_CONTEXT_MAJOR_VERSION, 2 ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_CONTEXT_MINOR_VERSION, 0 ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_DOUBLEBUFFER, 1 ); SDL2.SDL_GL_SetAttribute( SDL2.SDL_GL_DEPTH_SIZE, 24 ); w = SDL2.SDL_CreateWindow( "SDL2_14_OpenGL_ES_Gears", SDL2.SDL_WINDOWPOS_CENTERED, SDL2.SDL_WINDOWPOS_CENTERED, (int)sizeX, (int)sizeY, SDL2.SDL_WINDOW_OPENGL | SDL2.SDL_WINDOW_RESIZABLE ); // Check for a null reference. if ( w == null ) { // Output an error message. Stream.writeLine( "Failed to create window: " + SDL2.SDL_GetError( ) ); // Abort the application. return; } r = SDL2.SDL_CreateRenderer( w, -1, SDL2.SDL_RENDERER_ACCELERATED | SDL2.SDL_RENDERER_PRESENTVSYNC ); c = SDL2.SDL_GL_CreateContext( w ); keyboardState = SDL2.SDL_GetKeyboardState( ); gearsInit( ); gearsReshape( ); // Check for the emscripten environment. if ( emscriptenActive ) { int simulate_infinite_loop = 1; // Call the function repeatedly. int fps = -1; // Call the function as fast as the browser wants to render (typically 60fps). Emscripten.setMainLoop( renderFrame, fps, simulate_infinite_loop ); } else { // Rendering and event processing loop. do { renderFrame( ); } while( !done ); } // Shutdown app. OpenGL.glDeleteProgram( shaderProgram ); OpenGL.glDeleteShader( vertShader ); OpenGL.glDeleteShader( fragShader ); gear1.deleteGear( ); gear2.deleteGear( ); gear3.deleteGear( ); SDL2.SDL_GL_DeleteContext( c ); SDL2.SDL_DestroyRenderer( r ); SDL2.SDL_DestroyWindow( w ); SDL2.SDL_Quit( ); } // Resize event callback. public static bool resizeEventCallback( int eventType, EmscriptenUiEvent uiEvent, Object userObject ) { // Diagnostic code. /* Stream.writeLine( "resizeEventCallback - called" ); Stream.writeLine( "eventType = " + Integer.toString( eventType ) ); Stream.writeLine( "uiEvent.detail = " + Integer.toString( uiEvent.detail ) ); Stream.writeLine( "uiEvent.documentBodyClientWidth = " + Integer.toString( uiEvent.documentBodyClientWidth ) ); Stream.writeLine( "uiEvent.documentBodyClientHeight = " + Integer.toString( uiEvent.documentBodyClientHeight ) ); Stream.writeLine( "uiEvent.windowInnerWidth = " + Integer.toString( uiEvent.windowInnerWidth ) ); Stream.writeLine( "uiEvent.windowInnerHeight = " + Integer.toString( uiEvent.windowInnerHeight ) ); Stream.writeLine( "uiEvent.windowOuterWidth = " + Integer.toString( uiEvent.windowOuterWidth ) ); Stream.writeLine( "uiEvent.windowOuterHeight = " + Integer.toString( uiEvent.windowOuterHeight ) ); Stream.writeLine( "uiEvent.scrollTop = " + Integer.toString( uiEvent.scrollTop ) ); Stream.writeLine( "uiEvent.scrollLeft = " + Integer.toString( uiEvent.scrollLeft ) ); Stream.writeLine( "userObject = " + (String)userObject );*/ // Check for full-window mode. if ( fullWindowMode ) { // Get the new size. sizeX = uiEvent.windowInnerWidth; sizeY = uiEvent.windowInnerHeight; // Update the canvas size. Emscripten.setCanvasSize( sizeX, sizeY ); // Update the SDL window size. SDL2.SDL_SetWindowSize( w, sizeX, sizeY ); // Reshape the gears. gearsReshape( ); } // Return 'true' for handling the event. return true; } public static void renderFrame( ) { SDL_Event e = SDL2.SDL_PollEvent( ); if ( e != null ) { switch ( e.id ) { case SDL2.SDL_WINDOWEVENT : { onWindowEvent( (SDL_WindowEvent)e ); break; } case SDL2.SDL_KEYDOWN : { // Cast to a keyboard event. SDL_KeyboardEvent kbEvent = (SDL_KeyboardEvent)e; // Switch on the event key. switch ( kbEvent.sym ) { case SDL2.SDLK_w : { // Stream.writeLine( "'w' key pressed." ); if ( emscriptenActive ) { // Toggle full window mode. toggleFullWindowMode( ); } break; } case SDL2.SDLK_ESCAPE : { // Stream.writeLine( "'ESC' key pressed." ); if ( emscriptenActive ) { if ( fullWindowMode ) toggleFullWindowMode( ); // Exit full window mode. } else { done = true; // When running in a desktop app. } break; } } break; } case SDL2.SDL_QUIT : { // Stream.writeLine( "SDL_QUIT" ); done = true; // When running in a desktop app. break; } } } // Manually getting the state of the keys is more frequent and smoother, than keyboard events. if ( keyboardState[ SDL2.SDL_SCANCODE_LEFT ] != 0 ) view_rot[ 1 ] += 3.0; if ( keyboardState[ SDL2.SDL_SCANCODE_RIGHT ] != 0 ) view_rot[ 1 ] -= 3.0; if ( keyboardState[ SDL2.SDL_SCANCODE_UP ] != 0 ) view_rot[ 0 ] += 3.0; if ( keyboardState[ SDL2.SDL_SCANCODE_DOWN ] != 0 ) view_rot[ 0 ] -= 3.0; if ( keyboardState[ SDL2.SDL_SCANCODE_Z ] != 0 ) { // Brackets are needed here for the bitwise AND operator - // - because the equality operator has a slightly higher precedence. if ( ( SDL2.SDL_GetModState( ) & SDL2.KMOD_SHIFT ) != 0 ) { view_rot[ 2 ] -= 3.0; } else { view_rot[ 2 ] += 3.0; } } gearsDraw( ); SDL2.SDL_GL_SwapWindow( w ); // Update the gears animation and output the FPS. gearsUpdate( ); } public static void onWindowEvent( SDL_WindowEvent e ) { // Stream.writeLine( "SDL_WINDOWEVENT" ); // Check for a non null reference after the object cast. if ( e != null ) { switch ( e.event ) { case SDL2.SDL_WINDOWEVENT_RESIZED : { // Stream.writeLine( "SDL_WINDOWEVENT_RESIZED" ); // Update the size of the window. sizeX = e.data1; sizeY = e.data2; // Recalculate the projection matrix. gearsReshape( ); break; } } } } private static void gearsInit( ) { OpenGL.glEnable( OpenGL.GL_CULL_FACE ); OpenGL.glEnable( OpenGL.GL_DEPTH_TEST ); // The shaders. String vertCode = "attribute vec3 position; \n" + "attribute vec3 normal; \n" + " \n" + "uniform mat4 ModelViewProjectionMatrix; \n" + "uniform mat4 NormalMatrix; \n" + "uniform vec4 LightSourcePosition; \n" + "uniform vec4 MaterialColor; \n" + " \n" + "varying vec4 Color; \n" + " \n" + "void main( void ) \n" + "{ \n" + " // Transform the normal to eye coordinates. \n" + " vec3 N = normalize( vec3( NormalMatrix * vec4( normal, 1.0 ) ) ); \n" + " \n" + " // The LightSourcePosition is actually its direction for directional light. \n" + " vec3 L = normalize( LightSourcePosition.xyz ); \n" + " \n" + " // Multiply the diffuse value by the vertex color (which is fixed in this case) \n" + " // to get the actual color that we will use to draw this vertex with. \n" + " float diffuse = max( dot( N, L ), 0.0 ); \n" + " Color = diffuse * MaterialColor; \n" + " \n" + " // Transform the position to clip coordinates. \n" + " gl_Position = ModelViewProjectionMatrix * vec4( position, 1.0 ); \n" + "} "; String fragCode = "#ifdef GL_ES \n" + "precision mediump float; \n" + "#endif \n" + "varying vec4 Color; \n" + " \n" + "void main( void ) \n" + "{ \n" + " gl_FragColor = Color; \n" + "} "; vertShader = OpenGL.glCreateShader( OpenGL.GL_VERTEX_SHADER ); OpenGL.glShaderSource( vertShader, vertCode ); OpenGL.glCompileShader( vertShader ); fragShader = OpenGL.glCreateShader( OpenGL.GL_FRAGMENT_SHADER ); OpenGL.glShaderSource( fragShader, fragCode ); OpenGL.glCompileShader( fragShader ); shaderProgram = OpenGL.glCreateProgram( ); OpenGL.glAttachShader( shaderProgram, vertShader ); OpenGL.glAttachShader( shaderProgram, fragShader ); OpenGL.glLinkProgram( shaderProgram ); OpenGL.glUseProgram( shaderProgram ); // Get the locations of the uniforms so we can access them. modelViewProjectionMatrix_location = OpenGL.glGetUniformLocation( shaderProgram, "ModelViewProjectionMatrix" ); normalMatrix_location = OpenGL.glGetUniformLocation( shaderProgram, "NormalMatrix" ); lightSourcePosition_location = OpenGL.glGetUniformLocation( shaderProgram, "LightSourcePosition" ); materialColor_location = OpenGL.glGetUniformLocation( shaderProgram, "MaterialColor" ); // The direction of the directional light for the scene. float[] lightSourcePosition = { 5.0, 5.0, 10.0, 1.0 }; // Set the view rotation. view_rot = { 20.0f, 30.0f, 0.0f }; // Set the angle. angle = 0.0f; // Set the LightSourcePosition uniform which is constant throught the program. OpenGL.glUniform4fv( lightSourcePosition_location, 1, lightSourcePosition ); // Create the gears. gear1 = new Gear( 1.0f, 4.0f, 1.0f, 20, 0.7f ); gear2 = new Gear( 0.5f, 2.0f, 2.0f, 10, 0.7f ); gear3 = new Gear( 1.3f, 2.0f, 0.5f, 10, 0.7f ); } private static void gearsReshape( ) { setPerspective( (uint)sizeX, (uint)sizeY ); // Set the viewport. OpenGL.glViewport( 0, 0, sizeX, sizeY ); } private static void setPerspective( uint width, uint height ) { // Set the projection matrix. projectionMatrix = MatrixMath.perspective( 45.0, width / (float)height, 1.0, 1024.0 ); } private static void gearsDraw( ) { float[] red = { 0.8f, 0.1f, 0.0f, 1.0f }; float[] green = { 0.0f, 0.8f, 0.2f, 1.0f }; float[] blue = { 0.2f, 0.2f, 1.0f, 1.0f }; float[] transform = MatrixMath.identity( ); OpenGL.glClearColor( 0.5f, 0.5f, 0.5f, 0.0f ); OpenGL.glClear( OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT ); // Translate and rotate the view. transform = MatrixMath.translate( transform, 0, 0, -20 ); transform = MatrixMath.rotate( transform, (float)( 2 * Math.PI * (double)view_rot[ 0 ] / 360.0 ), 1, 0, 0 ); transform = MatrixMath.rotate( transform, (float)( 2 * Math.PI * (double)view_rot[ 1 ] / 360.0 ), 0, 1, 0 ); transform = MatrixMath.rotate( transform, (float)( 2 * Math.PI * (double)view_rot[ 2 ] / 360.0 ), 0, 0, 1 ); // Draw the gears. gear1.drawGear( transform, -3.0f, -2.0f, angle, red, projectionMatrix, modelViewProjectionMatrix_location, normalMatrix_location, materialColor_location ); gear2.drawGear( transform, 3.1f, -2.0f, -2 * angle - 9.0f, green, projectionMatrix, modelViewProjectionMatrix_location, normalMatrix_location, materialColor_location ); gear3.drawGear( transform, -3.1f, 4.2f, -2 * angle - 25.0f, blue, projectionMatrix, modelViewProjectionMatrix_location, normalMatrix_location, materialColor_location ); } private static void gearsUpdate( ) { // Get the time elapsed via SDL. double dt, t = SDL2.SDL_GetTicks( ) / 1000.0; if ( tRot0 < 0.0 ) { tRot0 = t; } dt = t - tRot0; tRot0 = t; // advance rotation for next frame. angle += (float)( 70.0 * dt ); // 70 degrees per second. if ( angle > 3600.0f ) { angle -= 3600.0f; } ++frames; if ( tRate0 < 0.0 ) { tRate0 = t; } // Every five seconds. if ( t - tRate0 >= 5.0 ) { float seconds = (float)( t - tRate0 ); float fps = (float)( frames / seconds ); Stream.writeLine( Integer.toString( frames ) + " frames in " + Float.toString( seconds ) + " seconds = " + Float.toString( fps ) + " FPS" ); tRate0 = t; frames = 0; } } // Toggle full window mode. public static void toggleFullWindowMode( ) { // Check the full window mode flag. if ( fullWindowMode ) { // Restore the previous canvas size. sizeX = prevSizeX; sizeY = prevSizeY; SDL2.SDL_SetWindowSize( w, sizeX, sizeY ); gearsReshape( ); // Exit full window mode. Emscripten.exitFullWindowMode( ); } else { // Backup the previous canvas size. prevSizeX = sizeX; prevSizeY = sizeY; sizeX = Emscripten.getClientWidth( ); sizeY = Emscripten.getClientHeight( ); // Enter full window mode. Emscripten.enterFullWindowMode( ); SDL2.SDL_SetWindowSize( w, sizeX, sizeY ); gearsReshape( ); } // Toggle the flag. fullWindowMode = !fullWindowMode; } } class Gear { // Gear constants. private int stripsPerTooth; private int verticesPerTooth; private int gearVertexStride; // The array of vertices comprising the gear. private float[][] vertices; // The number of vertices comprising the gear. private int nVertices; // The array of triangle strips comprising the gear. private VertexStrip[] strips; // The number of triangle strips comprising the gear. private int nstrips; // The Vertex Buffer Object holding the vertices in the graphics card. private uint[] vbos; public Gear( float inner_radius, float outer_radius, float width, int teeth, float tooth_depth ) { // Set the gear constants. stripsPerTooth = 7; verticesPerTooth = 34; gearVertexStride = 6; double[] s, c; // Sin & Cos arrays for various needed angles. float[] normal; // Calculate the radii used in the gear. float r0 = inner_radius; float r1 = outer_radius - tooth_depth / 2.0f; float r2 = outer_radius + tooth_depth / 2.0f; // Allocate memory for the triangle strip information. nstrips = stripsPerTooth * teeth; strips = new VertexStrip[ nstrips ]; for ( int i = 0; i < nstrips; ++i ) strips[ i ] = new VertexStrip( ); // Allocate memory for the vertices. vertices = new float[ verticesPerTooth * teeth ][ gearVertexStride ]; int cur_strip = 0; // The current strip. int v_i = 0; // The index of the current vertex. double da = 2.0 * Math.PI / teeth / 4.0; // Iterate through the teeth. for ( int i = 0; i < teeth; ++i ) { // Calculate needed sin/cos for varius angles. s = new double[ 5 ]; c = new double[ 5 ]; initSinCos( i, teeth, da, s, c ); // Create the 7 points (only x,y coords) used to draw a tooth. GearPoint[] p = { new GearPoint( r2, 1, c, s ), // 0 new GearPoint( r2, 2, c, s ), // 1 new GearPoint( r1, 0, c, s ), // 2 new GearPoint( r1, 3, c, s ), // 3 new GearPoint( r0, 0, c, s ), // 4 new GearPoint( r1, 4, c, s ), // 5 new GearPoint( r0, 4, c, s ) // 6 }; // Front face. startStrip( cur_strip, v_i ); normal = { 0, 0, 1.0 }; setGearVetex( v_i++, p, 0, +1, width, normal ); setGearVetex( v_i++, p, 1, +1, width, normal ); setGearVetex( v_i++, p, 2, +1, width, normal ); setGearVetex( v_i++, p, 3, +1, width, normal ); setGearVetex( v_i++, p, 4, +1, width, normal ); setGearVetex( v_i++, p, 5, +1, width, normal ); setGearVetex( v_i++, p, 6, +1, width, normal ); cur_strip = endStrip( cur_strip, v_i ); // Inner face. startStrip( cur_strip, v_i ); v_i = quadWithNormal( 4, 6, v_i, p, width, normal ); cur_strip = endStrip( cur_strip, v_i ); // Back face. startStrip( cur_strip, v_i ); normal = { 0, 0, -1.0 }; setGearVetex( v_i++, p, 6, -1, width, normal ); setGearVetex( v_i++, p, 5, -1, width, normal ); setGearVetex( v_i++, p, 4, -1, width, normal ); setGearVetex( v_i++, p, 3, -1, width, normal ); setGearVetex( v_i++, p, 2, -1, width, normal ); setGearVetex( v_i++, p, 1, -1, width, normal ); setGearVetex( v_i++, p, 0, -1, width, normal ); cur_strip = endStrip( cur_strip, v_i ); // Outer face. startStrip( cur_strip, v_i ); v_i = quadWithNormal( 0, 2, v_i, p, width, normal ); cur_strip = endStrip( cur_strip, v_i ); startStrip( cur_strip, v_i ); v_i = quadWithNormal( 1, 0, v_i, p, width, normal ); cur_strip = endStrip( cur_strip, v_i ); startStrip( cur_strip, v_i ); v_i = quadWithNormal( 3, 1, v_i, p, width, normal ); cur_strip = endStrip( cur_strip, v_i ); startStrip( cur_strip, v_i ); v_i = quadWithNormal( 5, 3, v_i, p, width, normal ); cur_strip = endStrip( cur_strip, v_i ); } // Set the number of vertices. nVertices = v_i; // Store the vertices in a vertex buffer object (VBO). vbos = new uint[ 1 ]; OpenGL.glGenBuffers( 1, vbos ); OpenGL.glBindBuffer( OpenGL.GL_ARRAY_BUFFER, vbos[ 0 ] ); float[] verticesFlat = createFlatArray( vertices ); OpenGL.glBufferData( OpenGL.GL_ARRAY_BUFFER, verticesFlat, OpenGL.GL_STATIC_DRAW ); } private float[] createFlatArray( float[][] vertices ) { float[] verticesFlat = new float[ (int)vertices.length * gearVertexStride ]; for ( int i = 0; i < (int)vertices.length; ++i ) { for ( int j = 0; j < gearVertexStride; ++j ) { verticesFlat[ ( i * gearVertexStride ) + j ] = vertices[ i ][ j ]; } } return verticesFlat; } private void initSinCos( int i, int teeth, double da, double[] s, double[] c ) { for ( uint j = 0; j < 5; ++j ) { double angle = i * 2.0 * Math.PI / teeth + da * j; s[ j ] = Math.sin( angle ); c[ j ] = Math.cos( angle ); } } private void startStrip( int cur_strip, int v_i ) { // Set the first vertex index for the current strip. strips[ cur_strip ].first = v_i; } // Fills a specified gear vertex. private void fillGearVertex( float[][] v, int i, float x, float y, float z, float[] n ) { v[ i ][ 0 ] = x; v[ i ][ 1 ] = y; v[ i ][ 2 ] = z; v[ i ][ 3 ] = n[ 0 ]; v[ i ][ 4 ] = n[ 1 ]; v[ i ][ 5 ] = n[ 2 ]; } private void setGearVetex( int v_i, GearPoint[] p, int point, int sign, float width, float[] normal ) { fillGearVertex( vertices, v_i, p[ point ].x, p[ point ].y, sign * width * 0.5f, normal ); } // Return the updated value of the current strip. private int endStrip( int cur_strip, int v_i ) { // Set the vertex count for the current strip. strips[ cur_strip ].count = v_i - strips[ cur_strip ].first; // Increment to the next strip. return ++cur_strip; } private int quadWithNormal( int p1, int p2, int v_i, GearPoint[] p, float width, float[] normal ) { normal = { p[ p1 ].y - p[ p2 ].y, -( p[ p1 ].x - p[ p2 ].x ), 0 }; setGearVetex( v_i++, p, p1, -1, width, normal ); setGearVetex( v_i++, p, p1, 1, width, normal ); setGearVetex( v_i++, p, p2, -1, width, normal ); setGearVetex( v_i++, p, p2, 1, width, normal ); // Return the updated vertex index. return v_i; } public void drawGear( float[] transform, float x, float y, float angle, float[] color, float[] projectionMatrix, int modelViewProjectionMatrix_location, int normalMatrix_location, int materialColor_location ) { float[] model_view; float[] normal_matrix; float[] model_view_projection; // Translate and rotate the gear. model_view = MatrixMath.translate( transform, x, y, 0 ); model_view = MatrixMath.rotate( model_view, (float)( 2 * Math.PI * angle / 360.0 ), 0, 0, 1 ); // Create and set the ModelViewProjectionMatrix. model_view_projection = MatrixMath.multiply( projectionMatrix, model_view ); OpenGL.glUniformMatrix4fv( modelViewProjectionMatrix_location, 1, false, model_view_projection ); // Create and set the NormalMatrix. It's the inverse transpose of the ModelView matrix. normal_matrix = MatrixMath.invert( model_view ); normal_matrix = MatrixMath.transpose( normal_matrix ); OpenGL.glUniformMatrix4fv( normalMatrix_location, 1, false, normal_matrix ); // Set the gear color. OpenGL.glUniform4fv( materialColor_location, 1, color ); // Set the vertex buffer object to use. OpenGL.glBindBuffer( OpenGL.GL_ARRAY_BUFFER, vbos[ 0 ] ); // Set up the position of the attributes in the vertex buffer object. OpenGL.glVertexAttribPointer( 0, 3, OpenGL.GL_FLOAT, false, 6 * 4, 0 ); OpenGL.glVertexAttribPointer( 1, 3, OpenGL.GL_FLOAT, false, 6 * 4, 3 * 4 ); // Enable the attributes. OpenGL.glEnableVertexAttribArray( 0 ); OpenGL.glEnableVertexAttribArray( 1 ); // Draw the triangle strips that comprise the gear. for ( int n = 0; n < nstrips; ++n ) { OpenGL.glDrawArrays( OpenGL.GL_TRIANGLE_STRIP, strips[ n ].first, strips[ n ].count ); } // Disable the attributes. OpenGL.glDisableVertexAttribArray( 1 ); OpenGL.glDisableVertexAttribArray( 0 ); } public void deleteGear( ) { // Free the gear's VBO. OpenGL.glDeleteBuffers( 1, vbos ); } } // Class describing the vertices in triangle strip. class VertexStrip { // The first vertex in the strip. public int first; // The number of consecutive vertices in the strip after the first. public int count; } // Gear point class (only for OpenGL gears). class GearPoint { public float x; public float y; public GearPoint( double r, int da, double[] c, double[] s ) { x = (float)( r * c[ da ] ); y = (float)( r * s[ da ] ); } } class MatrixMath { // Creates an identity 4x4 matrix. public static float[] identity( ) { float[] m = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 }; return m; } /** * Calculate a perspective projection transformation. * * fovy : the field of view in the y direction. * aspect : the view aspect ratio. * zNear : the near clipping plane. * zFar : the far clipping plane. */ public static float[] perspective( float fovy, float aspect, float zNear, float zFar ) { float[] m = identity( ); double sine, cosine, cotangent, deltaZ; double radians = (double)fovy / 2 * Math.PI / 180; deltaZ = zFar - zNear; sine = Math.sin( radians ); cosine = Math.cos( radians ); if ( ( deltaZ == 0 ) || ( sine == 0 ) || ( aspect == 0 ) ) { // Return null for an error. return null; } cotangent = cosine / sine; m[ 0 ] = (float)( cotangent / aspect ); m[ 5 ] = (float)cotangent; m[ 10 ] = (float)( -( (double)zFar + (double)zNear ) / deltaZ ); m[ 11 ] = -1; m[ 14 ] = (float)( -2 * (double)zNear * (double)zFar / deltaZ ); m[ 15 ] = 0; return m; } // Translates a 4x4 matrix. public static float[] translate( float[] m, float x, float y, float z ) { // Create the translation matrix. float[] t = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 }; // Multiply and return the result. return multiply( m, t ); } // Rotates a 4x4 matrix. public static float[] rotate( float[] m, double angle, double x, double y, double z ) { double s = Math.sin( angle ); double c = Math.cos( angle ); // Create the rotation matrix. float[] r = { (float)( x * x * ( 1 - c ) + c ), (float)( y * x * ( 1 - c ) + z * s ), (float)( x * z * ( 1 - c ) - y * s ), 0, (float)( x * y * ( 1 - c ) - z * s ), (float)( y * y * ( 1 - c ) + c ), (float)( y * z * ( 1 - c ) + x * s ), 0, (float)( x * z * ( 1 - c ) + y * s ), (float)( y * z * ( 1 - c ) - x * s ), (float)( z * z * ( 1 - c ) + c ), 0, 0, 0, 0, 1 }; // Multiply and return the result. return multiply( m, r ); } // Inverts a 4x4 matrix. public static float[] invert( float[] m ) { float[] t = identity( ); // Extract and invert the translation part 't'. The inverse of a // translation matrix can be calculated by negating the translation // coordinates. t[ 12 ] = -m[ 12 ]; t[ 13 ] = -m[ 13 ]; t[ 14 ] = -m[ 14 ]; // Invert the rotation part 'r'. The inverse of a rotation matrix is // equal to its transpose. m[ 12 ] = m[ 13 ] = m[ 14 ] = 0; m = transpose( m ); // inv( m ) = inv( r ) * inv( t ) return multiply( m, t ); } // Transposes a 4x4 matrix. public static float[] transpose( float[] m ) { float[] t = { m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ], m[ 1 ], m[ 5 ], m[ 9 ], m[ 13 ], m[ 2 ], m[ 6 ], m[ 10 ], m[ 14 ], m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ] }; return t; } // Blocked matrix multiplication algorithm. public static float[] multiply( float[] m, float[] n ) { float[] tmp = new float[ 16 ]; for ( int i = 0; i < 16; ++i ) { tmp[ i ] = 0; int row = i / 4; int column = i % 4; for ( int j = 0; j < 4; ++j ) { tmp[ i ] += n[ row * 4 + j ] * m[ j * 4 + column ]; } } return tmp; } }